package bento.api.db;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import bento.api.AbstractBentoClient;
import bento.api.IBentoClient.IAcountManager.IAcount;
import bento.api.IBentoClient.IStoreManager.IStore;
import bento.api.IBentoClient.IStoreManager.IStore.IBentoManager;
import bento.api.IQuerier;
import bento.api.IQuerier.IAppendThen;
import bento.api.IQuerier.IField;
import bento.api.IQuerier.IWhereThen;
import bento.api.ITransactor;
import bento.api.db.BentoDb.AbstractTable;
import bento.api.db.BentoDb.StoresTable;
import bento.api.db.BentoDb.StoresTable.Row;
import bento.utils.IPredicate;

public class BentoClient extends AbstractBentoClient {

    private IStoreManager stores= new StoreManager();
    
    @Override
    public IGate getGate() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public IAcount getAcount() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public IAcountManager getAcounts() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public IStoreManager getStores() {
        return stores;
    }

    @Override
    public IOrderManager getOrders() {
        // TODO Auto-generated method stub
        return null;
    }
    
    private static abstract class AbstractManager<TElem extends IManager.IElem, TWrapped> 
                   extends AbstractBentoClient.AbstractManager<TElem> {
        
        protected static abstract class Field<TValue, TWrapped> implements IField<TValue> {
            private String name;
            
            public abstract TValue getValue(TWrapped row);
            
            @Override
            public final String getName() {
                return name;
            }
            
            public Field(String name) {
                this.name= name;
            }
        }
        
        protected static abstract class Querier<TElem extends IManager.IElem, TWrapped extends AbstractTable.Row> implements IQuerier<TElem> {
            private AbstractTable<TWrapped> dbSource;
            private List<IPredicate<TWrapped>> lst_filter= new ArrayList<>();
            
            protected abstract TElem wrap(TWrapped wrapped);
            
            protected Querier(AbstractTable<TWrapped> dbSource) {
                this.dbSource= dbSource;
            }
            
            @Override
            public Iterable<TElem> query() {
                Connection con= BentoDb.doConnect();
                
                List<TElem> lst_result= new ArrayList<>();
                for (TWrapped row : dbSource.getRows(con)) {
                    boolean isOk= true;
                    for (IPredicate<TWrapped> filter : lst_filter) {
                        if (!filter.apply(row)){
                            isOk= false;
                            continue;
                        }
                    }
                    
                    if (isOk) lst_result.add(wrap(row));
                }
                try {
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                
                return lst_result;
            }

            @Override
            public <T> IWhereThen<IQuerier<TElem>, T> where(IField<T> field) {
                final Field<T, TWrapped> f2= (Field<T, TWrapped>) field;
                
                return new IWhereThen<IQuerier<TElem>, T>() {

                    @Override
                    public IAppendThen<IQuerier<TElem>, T> equalsTo(T value) {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public IAppendThen<IQuerier<TElem>, T> less(T value) {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public IAppendThen<IQuerier<TElem>, T> greater(T value) {
                        // TODO Auto-generated method stub
                        return null;
                    }

                    @Override
                    public IAppendThen<IQuerier<TElem>, T> contain(final T value) {
                        lst_filter.add(new IPredicate<TWrapped>() {
                            @Override
                            public boolean apply(TWrapped row) {
                                return f2.getValue(row).toString().contains(value.toString());
                            }
                        });
                        return null;
                    }
                };
            }

            @Override
            public IQuerier<TElem> reset() {
                lst_filter.clear();
                return this;
            }
        }
        
        protected static abstract class _Transactor<
                                    TElem extends IManager.IElem, 
                                    _TWrapped extends AbstractTable.Row,
                                    TWrapped extends _TWrapped
                                  > 
                                  implements ITransactor<TElem> {
            
              private AbstractTable<_TWrapped> dbSource;
              
              public abstract TWrapped unwrap(TElem elem);
              public abstract TWrapped towraped(TElem elem);
              
              
        }
        
        protected static abstract class Transactor<
                                            TElem extends IManager.IElem, 
                                            TWrapped_ extends AbstractTable.Row,
                                            TWrapped extends TWrapped_
                                        > 
                                        implements ITransactor<TElem> {
            private AbstractTable<TWrapped_> dbSource;
            
            // TODO
            public abstract TWrapped unwrap(TElem elem);
            public abstract TWrapped towraped(TElem elem);
            
            @Override
            public boolean add(TElem elem) {
                Connection con= BentoDb.doConnect();
                try {
                    TWrapped wraped= towraped(elem);
                    
                    if (dbSource.add(con, wraped)==null) return false;
                } catch (Exception e) {
                    return false;
                }
                
                try {
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                
                return true;
            }

            @Override
            public boolean remove(TElem elem) {
                Connection con= BentoDb.doConnect();
                
                boolean r = dbSource.remove(con, unwrap(elem));
                try {
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                return r;
            }

            @Override
            public boolean update(TElem elem) {
                Connection con= BentoDb.doConnect();
                try {
                    TWrapped wraped= towraped(elem);
                    dbSource.replace(con, wraped);
                } catch (Exception e) {
                    return false;
                }
                
                try {
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                return true;
            }
            
            public Transactor(AbstractTable<TWrapped_> dbSource) {
                this.dbSource= dbSource;
            }
        }
        
        protected static class Wrapper<TWrapped> implements IWrapper<TWrapped> {
            private TWrapped value;
            
            @Override
            public TWrapped get() {
                return value;
            }
            
            
            public Wrapper<TWrapped> reset(TWrapped value) {
                this.value= value;
                return this;
            }
            
            public Wrapper(TWrapped value) {
                reset(value);
            }
        }
    }
    
    private static class StoreManager 
                     extends AbstractManager<IStore, BentoDb.StoresTable.Row>
                     implements IStoreManager{

        private StoresTable dbSource= BentoDb.StoresTable;
        
        private ITransactor<IStore> transactor= new Transactor<IStore, BentoDb.StoresTable.Row,BentoDb.StoresTable.Row>(dbSource) {
            @Override
            public Row unwrap(IStore elem) {
                WrapperStore w= (WrapperStore) elem;
                return w.get();
            }

            @Override
            public Row towraped(IStore elem) {
                Row wrapped= unwrap(elem);
                Row tmp= new Row();
                tmp.setId(wrapped.getId());
                tmp.setHelp(wrapped.getHelp());
                tmp.setName(elem.getName());
                tmp.setTel(elem.getTel());
                
                // TODO: unwrap(elem).getBentos()
                return tmp;
            }
        };
        
        private IQuerier<IStore> querier= new Querier<IStore, BentoDb.StoresTable.Row>(dbSource) {
            @Override
            protected IStore wrap(Row wrapped) {
                return new WrapperStore(wrapped, transactor);
            }
        };
        
        private IField<String> nameField= new Field<String, BentoDb.StoresTable.Row>("Name") {
            @Override
            public String getValue(BentoDb.StoresTable.Row row) {
                return row.getName();
            }
        };
        private IField<String> telField= new Field<String, BentoDb.StoresTable.Row>("Tel") {
            @Override
            public String getValue(BentoDb.StoresTable.Row row) {
                return row.getTel();
            }
        };

        @Override
        public IStore create() {
            return new WrapperStore(transactor);
        }
        
        @Override
        public String toString() {
            return "{size=" + size() + ", " + super.toString() + "}";
        }
        
        @Override
        public IField<String> getNameField() {
            return nameField;
        }

        @Override
        public IField<String> getTelField() {
            return telField;
        }
        
        public StoreManager() {
            setQuerier(querier);
            setTransactor(transactor);
        }
        
        private static class Store extends bento.api.db.BentoDb.StoresTable.Row {
            private IBentoManager bentos;
            
            public IBentoManager getBentos() {
                return bentos;
            }
            
            
        }
        
        private static class WrapperStore extends Wrapper<BentoDb.StoresTable.Row> implements IStore {
            private String name;
            private String tel;
            
            private ITransactor<IStore> transactor;
            
            @Override
            public void refresh() {
                name= get().getName();
                tel= get().getTel();
                // TODO
                //get().getBentos().refresh();
            }
        
            @Override
            public void commit() {
                transactor.update(this);
            }
        
            @Override
            public String getName() {
                return name;
            }
        
            @Override
            public IStore setName(String name) {
                this.name= name;
                return this;
            }
        
            @Override
            public String getTel() {
                return tel;
            }
        
            @Override
            public IStore setTel(String tel) {
                this.tel= tel;
                return this;
            }
        
            @Override
            public IBentoManager getBentos() {
                // TODO
                //return get().getBentos();
                return null;
            }
            
            @Override
            public String toString() {
                // TODO
                String s= "{name='" + name + "', tel='" + tel + "'";
                //s+= ", bentos=" + get().getBentos().toString() + "}";
                s+= "}";
                
                return s;
            }
            
            public WrapperStore(ITransactor<IStore> transactor) {
                this(new BentoDb.StoresTable.Row(), transactor);
            }
            
            public WrapperStore(BentoDb.StoresTable.Row store, ITransactor<IStore> transactor) {
                super(store);
                this.transactor= transactor;
                refresh();
            }
        }
        
        private static class BentoManager extends AbstractManager<IBentoManager.IBento, BentoManager.Bento> 
                                          implements IBentoManager {
            
            @Override
            public IBento create() {
                // TODO
                //return new WrapperBento(transactor);
                return null;
            }
            
            @Override
            public String toString() {
                return "{size=" + size() + ", " + super.toString() + "}";
            }
            
            public BentoManager() {
                // TODO
                //setQuerier(querier);
                //setTransactor(transactor);
                
            }
            
            public static class Bento implements bento.types.IStore.IBento {
                private String name;
                private int price;
                
                @Override
                public String getName() {
                    return name;
                }

                @Override
                public bento.types.IStore.IBento setName(String name) {
                    this.name= name;
                    return this;
                }

                @Override
                public int getPrice() {
                    return price;
                }

                @Override
                public bento.types.IStore.IBento setPrice(int price) {
                    this.price= price;
                    return this;
                }

                public Bento() {
                    
                }
                
                public Bento(IBento elem) {
                    name= elem.getName();
                    price= elem.getPrice();
                }
            }
            
            private static class WrapperBento extends Wrapper<Bento> implements IBento {
                private String name;
                private int price;
                private ITransactor<IBento> transactor;
                
                @Override
                public void refresh() {
                    name= get().getName();
                    price= get().getPrice();
                }

                @Override
                public void commit() {
                    transactor.update(this);
                }

                @Override
                public String getName() {
                    return name;
                }

                @Override
                public IBento setName(String name) {
                    this.name= name;
                    return this;
                }

                @Override
                public int getPrice() {
                    return price;
                }

                @Override
                public IBento setPrice(int price) {
                    this.price= price;
                    return this;
                }
                
                @Override
                public String toString() {
                    return "{name='" + name + "', price=" + price + "}"; 
                }
                
                public WrapperBento(ITransactor<IBento> transactor) {
                    this(new Bento(), transactor);
                }
                
                public WrapperBento(Bento value, ITransactor<IBento> transactor) {
                    super(value);
                    this.transactor= transactor;
                    refresh();
                }
            }
        }

    }
//    
}
